import { until } from '@vueuse/core'
import { useAxios } from '@vueuse/integrations/useAxios'
import { toRef } from 'vue'
import {  randomKey } from '@/utils/tools'

const keysToLowerCase = (obj) => {
    return Object.keys(obj).reduce((result, key) => {
        //key首字母小写
        const newKey = key.replace(/^[A-Z]/, (match) => match.toLowerCase());
        result[newKey] = obj[key]; // 如果值也是对象，可以递归调用keysToLowerCase
        return result;
    }, {});
}

let instance = {}
let forceRefresh = ref(false)
export const setRequestInstance = (axiosInstance) => {
    instance = axiosInstance
}

export const setForceRefresh = (val) => {
    forceRefresh = toRef(val)
}

const requestSet = new Set()

export const getOptions = (options, getMethod = true) => {
    let _url = ""
    let _method = ""
    let _option = {}
    let index = 1
    for (let _opt of options) {
        if (typeof _opt === 'string' && index === 1) {
            _url = _opt
            index++
        } else if (typeof _opt === 'string' && index === 2) {
            _method = _opt
        } else if (typeof _opt === 'object') {
            _option = _opt
        }
    }

    if (!_url) {
        const { url: urlInOptions, ...otherOptions } = _option
        _url = urlInOptions
        _option = otherOptions
    }

    if (!_method && getMethod) {
        const { method: methodInOptions, ...otherOptions } = _option
        _method = methodInOptions || 'get'
        _option = otherOptions
    }
    return [_url, _method, _option]
}

/**
 * @param options:(url,method,options)  (options)
 * url和method参数可以省略，但如果有必须按顺序放在第一个和第二个参数，其他参数必须放在第三个参数
 * 没有的情况会从options中解构，如果options中的url和method是作为参数传递，那么就要把请求的url和method放在外面
 * 
 *        url 请求地址
 *        repeatDelay 拦截重复请求间隔  <0 不做任何拦截 默认0
 *        repeatAsThrow 是否抛出重复异常
 *        formData 是否使用formData请求
 *        method 请求方式
 *        defaultStatusInData   请求结果默认状态
 *        剩余其他的参数都作为请求参数
 */
const useRequest = (...options) => {
    let parseOptions = getOptions(options)
    const url = parseOptions[0]
    const method = parseOptions[1]
    let option = parseOptions[2]
    const { formData = false, repeatDelay = 0, repeatAsThrow = false, defaultStatusInData = true, timeout = 0, axiosInstance = instance, ...data } = option
    const parseOpt = {
        url: url,
        method: method,
        timeout: timeout
    }
    if (forceRefresh.value) data.forceRefresh = forceRefresh.value
    parseOpt[method === 'get' ? 'params' : 'data'] = keysToLowerCase(data)
    if (formData) parseOpt['headers'] = {
        'Content-Type': 'multipart/form-data; boundary=-'
    }

    const md5 = randomKey()
    if (requestSet.has(md5)) {
        const promise = new Promise((resolve, reject) => {
            if (repeatAsThrow) {
                reject({ message: '重复请求', url, param: data, status: false })
            } else {
                resolve({ message: '重复请求', url, param: data, status: false })
            }
        })
        return {
            then: (...args2) => promise.then(...args2),
            catch: (...args2) => promise.catch(...args2),
            abort: () => { },
            data: {}
        }
    }
    requestSet.add(md5)

    const { isFinished, response, error, abort, data: responseData } = useAxios(url, parseOpt, axiosInstance)
    until(isFinished).toBe(true).then(() => {
        setTimeout(() => {
            requestSet.delete(md5)
        }, repeatDelay);
    })
    const waitUntilFinished = () => new Promise((resolve, reject) => {
        until(isFinished).toBe(true).then(() => {
            try {
                if (response.value && response.value.status === undefined) {
                    response.value.status = defaultStatusInData
                }
            } catch (e) { }
            return error.value ? reject(error.value) : resolve(response.value)
        })
    })
    const promise = {
        then: (...args2) => waitUntilFinished().then(...args2),
        catch: (...args2) => waitUntilFinished().catch(...args2)
    }
    return { ...promise, abort, data: responseData }
}

["get", "post", "options"].forEach(method => {
    useRequest[method] = (...options) => {
        let option = getOptions(options, false)
        return useRequest(option[0], method, option[2])
    }
})

export default useRequest